Skip to content

Add GPU support: gpu_card / vgpu_profile data sources and GPU on service offerings - #315

Open
poddm wants to merge 14 commits into
apache:mainfrom
poddm:mp/service_gpus_clean
Open

Add GPU support: gpu_card / vgpu_profile data sources and GPU on service offerings#315
poddm wants to merge 14 commits into
apache:mainfrom
poddm:mp/service_gpus_clean

Conversation

@poddm

@poddm poddm commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds GPU support to the provider, aligned with the GPU/vGPU APIs introduced in
recent CloudStack releases. This lets operators discover GPU cards and vGPU
profiles and attach GPUs to service offerings.

What's included

New data sources

  • cloudstack_gpu_card — looks up a GPU card via the listGpuCards API.
    Exposes id, name, device_id, device_name, vendor_id, vendor_name.

  • cloudstack_vgpu_profile — looks up a vGPU profile via the listVgpuProfiles
    API. Exposes id, name, description, device_id, device_name,
    gpu_card_id, gpu_card_name, max_heads, max_resolution_x,
    max_resolution_y, max_vgpu_per_physical_gpu, vendor_id, vendor_name,
    video_ram.

    Both data sources support the standard filter block (regex matching on
    returned fields).

Service offering GPU configuration

  • Adds an optional gpu nested block to the service offering resources
    (constrained / fixed / unconstrained), with:
    • vgpu_profile_id (required) — vGPU profile to associate with the offering
    • count (optional) — number of GPUs assigned to the guest VM
    • display (optional) — whether the GPU is presented as a display device
    • All three force replacement when changed.

Docs

  • Website documentation for gpu_card and vgpu_profile data sources.

Dependency

  • Bumps github.com/apache/cloudstack-go/v2 from v2.18.1 to v2.19.1 for the
    GPU API bindings.

Example usage

data "cloudstack_gpu_card" "main" {
  filter {
    name  = "name"
    value = "Example Corp EX100GL \\[ExampleGPU 32GB\\]"
  }
}

data "cloudstack_vgpu_profile" "main" {
  filter {
    name  = "gpu_card_id"
    value = data.cloudstack_gpu_card.main.id
  }
  filter {
    name  = "name"
    value = "passthrough"
  }
}

resource "cloudstack_service_offering_constrained" "example" {
  name         = "example.gpu.offering"
  display_text = "Example GPU offering, vCPU 2-32, Memory 2G-128G"

  // compute
  cpu_speed      = 1024
  max_cpu_number = 32
  min_cpu_number = 2
  max_memory     = 131072
  min_memory     = 2048
  network_rate   = 10000

  // other
  disk_offering_id = var.disk_offering_id
  zone_ids         = var.zone_ids
  tags             = "EXAMPLE_STORAGE"
  host_tags        = "EXAMPLE_GPU"

  // Feature flags
  dynamic_scaling_enabled = true
  is_volatile             = false
  limit_cpu_use           = false
  offer_ha                = true

  gpu = {
    vgpu_profile_id = data.cloudstack_vgpu_profile.main.id
    count           = 1
    display         = true
  }
}

poddm added 2 commits August 12, 2026 12:57
(cherry picked from commit 0f8167f)
(cherry picked from commit b6de735)
@vishesh92
vishesh92 requested a lite review from Copilot August 17, 2026 09:56
Comment thread cloudstack/service_offering_schema.go
Comment thread cloudstack/service_offering_schema.go
@vishesh92

Copy link
Copy Markdown
Member

@poddm can you resolve the conflicts?
Can you also update the service offering documentation?
For the datasources, we seem to be filtering on the client side for some fields. It would be better to do the filtering on server side instead.
Ref:
https://cloudstack.apache.org/api/apidocs-4.22/apis/listGpuCards.html
https://cloudstack.apache.org/api/apidocs-4.22/apis/listVgpuProfiles.html

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds provider support for CloudStack GPU/vGPU discovery and service offering GPU configuration, aligning with newer CloudStack GPU APIs.

Changes:

  • Introduces cloudstack_gpu_card and cloudstack_vgpu_profile data sources with filter support.
  • Adds an optional gpu nested block to service offering resources (constrained/fixed/unconstrained).
  • Updates docs and bumps cloudstack-go dependency to include GPU API bindings.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
website/docs/d/vgpu_profile.html.markdown Adds documentation for the vGPU profile data source and its exported attributes.
website/docs/d/gpu_card.html.markdown Adds documentation for the GPU card data source and its exported attributes.
go.mod Bumps github.com/apache/cloudstack-go/v2 to a version that includes GPU bindings.
go.sum Updates module checksums for the dependency bump.
cloudstack/provider.go Registers the new GPU-related data sources in the provider.
cloudstack/data_source_cloudstack_vgpu_profile.go Implements the cloudstack_vgpu_profile data source and filtering logic.
cloudstack/data_source_cloudstack_vgpu_profile_test.go Adds acceptance test coverage for the vGPU profile data source.
cloudstack/data_source_cloudstack_gpu_card.go Implements the cloudstack_gpu_card data source and filtering logic.
cloudstack/data_source_cloudstack_gpu_card_test.go Adds acceptance test coverage for the GPU card data source.
cloudstack/service_offering_schema.go Adds the gpu nested block schema shared by service offering resources.
cloudstack/service_offering_models.go Adds the ServiceOfferingGpu model and wires it into the common resource model.
cloudstack/service_offering_util.go Adds common read/create param helpers for the service offering gpu block.
cloudstack/service_offering_constrained_resource.go Wires gpu block into constrained service offering create/read flows.
cloudstack/service_offering_constrained_resource_test.go Adds acceptance test coverage for constrained service offering GPU configuration.
cloudstack/service_offering_fixed_resource.go Wires gpu block into fixed service offering create/read flows.
cloudstack/service_offering_fixed_resource_test.go Adds acceptance test coverage for fixed service offering GPU configuration.
cloudstack/service_offering_unconstrained_resource.go Wires gpu block into unconstrained service offering create/read flows.
cloudstack/service_offering_unconstrained_resource_test.go Adds acceptance test coverage for unconstrained service offering GPU configuration.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cloudstack/data_source_cloudstack_vgpu_profile.go Outdated
Comment thread cloudstack/data_source_cloudstack_gpu_card.go Outdated
Comment thread cloudstack/service_offering_util.go Outdated
Comment thread cloudstack/data_source_cloudstack_vgpu_profile.go Outdated
}
}

return fmt.Errorf("no GPU cards found")
Comment thread cloudstack/service_offering_unconstrained_resource_test.go
@sureshanaparti sureshanaparti added this to the v0.7.0 milestone Aug 17, 2026
@sudo87 sudo87 removed this from the v0.7.0 milestone Aug 18, 2026
- Add Computed: true to count field to indicate server-managed attribute
- Add Computed: true and Default: false to display field for consistency
  with other boolean attributes in the schema
Review Fixes:
1. Fix filter panic in vGPU profile datasource
   - Safely validate field exists before accessing
   - Use fmt.Sprintf for safe type conversion
   - Return clear error for unknown filter fields

2. Fix filter panic in GPU card datasource
   - Same safety improvements as vGPU profile filters
   - Prevents panics on non-string fields or invalid names

3. Fix state drift detection in service offering GPU block
   - Explicitly set VgpuProfileId to null when empty
   - Explicitly set Count to null when 0
   - Allows drift detection when GPU config changes out-of-band

4. Update service offering documentation
   - Add GPU block examples to fixed/constrained/unconstrained offerings
   - Document GPU block attributes and defaults

Note: Client-side filtering remains; API doesn't expose server-side filter params
for GPU cards/vGPU profiles as suggested in review.
@sureshanaparti
sureshanaparti requested review from vishesh92 and a lite review from Copilot August 18, 2026 12:49
- Add testAccPreCheckGPU to provider_test.go for version validation
- Update GPU datasource tests to use testAccPreCheckGPU
- Create separate GPU test functions for service offerings
  - TestAccServiceOfferingFixed_GPU
  - TestAccServiceOfferingConstrained_GPU
  - TestAccServiceOfferingUnconstrained_GPU
- Tests will skip if CloudStack version < 4.22.0.0

This ensures GPU tests only run on CloudStack versions that support
the GPU API (4.22.0+).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.

Suppressed comments (5)

cloudstack/data_source_cloudstack_vgpu_profile.go:174

  • The regex is compiled for every filter evaluation and (as written) would be recompiled for every item in the API response. Precompile the regex patterns once per read (e.g., build a slice/map of compiled filters before iterating the returned profiles) to avoid repeated compilation overhead, especially if listVgpuProfiles returns many entries.
	for _, f := range filters.List() {
		filter := f.(map[string]interface{})
		r, err := regexp.Compile(filter["value"].(string))
		if err != nil {
			return false, fmt.Errorf("invalid regex: %s", err)
		}

cloudstack/data_source_cloudstack_gpu_card.go:127

  • The regex is compiled inside the filter application loop, which is called once per returned GPU card. Consider compiling all filter regexes once (before iterating csGpuCards.GpuCards) and reusing them for each card to reduce CPU overhead.
	for _, f := range filters.List() {
		filter := f.(map[string]interface{})
		r, err := regexp.Compile(filter["value"].(string))
		if err != nil {
			return false, fmt.Errorf("invalid regex: %s", err)
		}

website/docs/r/service_offering_constrained.html.markdown:45

  • The docs use block syntax (gpu { ... }), but the acceptance tests and PR description use object assignment (gpu = { ... }). Since the schema is implemented as a schema.SingleNestedAttribute (object attribute), the docs should match the correct configuration style (or the schema should be changed to a nested block type if block syntax is intended). Please update the service offering docs consistently (constrained/fixed/unconstrained) to avoid user confusion.
	gpu {
		vgpu_profile_id = "gpu-profile-uuid"
		count           = 1
		display         = false
	}

cloudstack/service_offering_schema.go:257

  • gpu.count should be validated to prevent invalid values (e.g., 0 or negative), which CloudStack is unlikely to accept for a GPU assignment count. Add an Int32 validator (e.g., at least 1) so bad configs fail fast during planning.
				"count": schema.Int32Attribute{
					Description: "the number of GPUs to assign to the guest VM",
					Optional:    true,
					Computed:    true,
					PlanModifiers: []planmodifier.Int32{
						int32planmodifier.RequiresReplace(),
					},
				},

cloudstack/service_offering_schema.go:266

  • In terraform-plugin-framework schemas, combining Computed: true with Default: ... is typically invalid/unsupported because defaults apply to optional attributes while computed values are set by the provider. Consider removing Computed: true (keep Optional + Default) or removing the Default and handling unknowns via plan modifiers/state to avoid schema validation/runtime errors.
				"display": schema.BoolAttribute{
					Description: "whether the GPU is presented as a display device to the guest VM",
					Optional:    true,
					Computed:    true,
					PlanModifiers: []planmodifier.Bool{
						boolplanmodifier.RequiresReplace(),
					},
					Default: booldefault.StaticBool(false),
				},

Comment thread cloudstack/data_source_cloudstack_vgpu_profile.go Outdated
Comment thread cloudstack/data_source_cloudstack_gpu_card.go Outdated
@sudo87

sudo87 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor
  1. Dead code: stateGpu.commonRead() results are discarded — state.ServiceOfferingGpu is never written back before resp.State.Set, so the drift-detection fix does nothing.
  2. Host-scoped vGPU IDs: data source returns an arbitrary per-host profile UUID with no host/zone scope or ambiguity check, so offerings get IDs that won't match the deployment host.
  3. Name vs ID confusion: docs feed vgpu_profile_id from data.cloudstack_vgpu_profile.id, tests feed the profile name — undefined, contradictory contract.
  4. False justification: listVgpuProfiles/listGpuCards DO support server-side filters (name, gpucardid, vendorid, ...) — the reflection filter layer is unjustified.

@poddm
poddm force-pushed the mp/service_gpus_clean branch from e449cd9 to 2fd9627 Compare August 18, 2026 15:16
@poddm
poddm marked this pull request as draft August 18, 2026 18:48
Comment thread cloudstack/service_offering_schema.go Outdated
},
"gpu": schema.SingleNestedAttribute{
Optional: true,
Attributes: map[string]schema.Attribute{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding or removing this block should result in a replace as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@vishesh92

Copy link
Copy Markdown
Member

@poddm just 2 comments. And for the tests to pass we will need to run discover GPUDevices command on the hosts.

@poddm

poddm commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @sudo87 — all four addressed:

  1. Dead code / no drift detectioncommonRead() now writes the nested gpu object back to state (state.ServiceOfferingGpu = obj) before resp.State.Set, and nulls vgpu_profile_id/count when the API returns empty/0, so out-of-band changes are detected.

  2. Host-scoped vGPU IDs / ambiguity — vGPU profiles are GPU-card–scoped, not host-scoped (listVgpuProfiles has no host/zone param), so the profile UUID is the correct global ID. The real gap is fixed: data sources now error on multiple matches (narrow via gpu_card_id) and reject duplicate filter names.

  3. Name vs ID confusion — tests no longer hardcode a profile name; they look it up via the data source (vgpu_profile_id = data.cloudstack_vgpu_profile.test.id) and assert with TestCheckResourceAttrPair. Tests, docs, schema, and API now all treat it as the UUID.

  4. Server-side filters — reflection/regex layer removed; filters map directly to listVgpuProfiles/listGpuCards params (id, name, gpu_card_id, vendor_id, device_id, device_name, keyword, active_only), combine as AND, and error on unsupported names (card namekeyword).

@poddm

poddm commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Good catch — fixed in [latest commit]. I added an object-level objectplanmodifier.RequiresReplace() on the gpu SingleNestedAttribute so that adding or removing the whole block (null ↔ set) now forces a replace, not just edits to the inner fields.

While I was at it, I applied the same object-level RequiresReplace() to the sibling nested blocks (disk_offering, disk_hypervisor, disk_storage), which had the same gap.

@poddm

poddm commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

@poddm just 2 comments. And for the tests to pass we will need to run discover GPUDevices command on the hosts.

I added a check for this.

@poddm
poddm marked this pull request as ready for review September 2, 2026 18:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants